Theming & Customization Guide
7-min read
Use this guide to change any part of Vyasa's look and behavior. It covers where to place CSS, how scoping works, which elements to target, and how to change behavior safely.
Vyasa has two different theming jobs to do. Sometimes you want to restyle the rendered article itself, for example changing paragraph spacing, code colors, or callout treatment for one folder. Other times you want to restyle the page around the article, for example loading a book-specific font, changing the viewport background, or recoloring the navbar and sidebars for one section. Those two jobs look similar from the outside, but they need different CSS loading behavior.
That is why Vyasa now separates scoped folder CSS from global folder CSS. Scoped CSS is for content-area styling and is attached to the current post section only. Global CSS is for page-level styling and is loaded as a normal stylesheet, so it can legally use html, body, @font-face, @keyframes, and other top-level CSS features.
1) Where to put your CSS URL copied
Vyasa loads custom CSS in this order (later wins):
- Framework CSS (bundled in the app)
- Root global CSS:
global.css,custom.css, orstyle.cssat your blog root - Folder global CSS:
global.cssinside a folder and its ancestor folders - Folder scoped CSS:
custom.cssorstyle.cssinside a folder and its ancestor folders
Your blog root is determined by VYASA_ROOT or a .vyasa config file. If neither is set, Vyasa uses the current working directory.
Root-level CSS (site-wide) URL copied
Create or edit:
/your-blog-root/global.css/your-blog-root/custom.css(preferred)/your-blog-root/style.css
Use global.css when you need true page-level CSS. Use root custom.css or style.css for the traditional site-wide stylesheet behavior.
Folder-level CSS (scoped) URL copied
Place a custom.css or style.css in any content folder to style the rendered post area for that folder and its subfolders.
Vyasa wraps that CSS inside a scoped selector:
#main-content.section-your-folder { ... }
This is for selectors that conceptually belong to the post content: headings, paragraphs, tables, images, code blocks, callouts, and other markdown output.
Folder-level global CSS URL copied
Place a global.css in a content folder when the folder needs to style the whole page shell.
Vyasa links that file as a normal stylesheet for every page in that folder subtree. That means it can safely define rules for:
html,body#page-container,#site-navbar,#posts-sidebar,#toc-sidebar@font-face@keyframes:rootcustom properties
This is the correct tool for book themes, app-like section chrome, or folder-specific fonts.
Example mental model URL copied
For demo/books/flat-land/:
global.csssets the parchment viewport background, navbar/sidebar chrome, and custom fontscustom.cssstyles the chapter page itself: the paper card, headings, paragraph rhythm, and images
If you remember only one rule, remember this: global.css is for the page, custom.css is for the post.
2a) Built-in previous/next navigation URL copied
Vyasa now renders previous/next links automatically for markdown files that have visible sibling markdown files in the same folder.
The pager follows the same folder order Vyasa uses elsewhere, including .vyasa order, sort rules, and visibility filtering.
The pager is added under the rendered article and uses these selectors:
.vyasa-prev-next
.vyasa-prev-link
.vyasa-next-link
This is especially useful for books, tutorials, and long-form docs where each folder is a linear reading sequence.
2) How to find the section scope class URL copied
Folder CSS is scoped to the #main-content element with a section class derived from the path.
Example:
demo/books/flat-land/chapter-01.md- Section class on
#main-contentbecomes:section-demo-books-flat-land
To confirm, inspect #main-content in your browser DevTools and copy the class.
3) DOM map (what you can target) URL copied
Use these ids/classes to style specific elements.
How to find selectors for obscure elements URL copied
If an element isn't listed here, you can find its selector quickly:
- Use DevTools first: right‑click the element → Inspect → note the
idor classes on the highlighted node. - Search in source: open
vyasa/core.py(live app) andvyasa/build.py(static build), then search for the element’s text or a nearby class name. - Search for ids/classes: in the repo, run a text search for the class or id you saw in DevTools.
- Follow the builder: most markup is created in
layout()ornavbar(); those functions assemble the page chrome and are the easiest places to trace where a class or id is set. - Look for generated HTML: if the element is created from Markdown, check render functions like
render_footnote_ref()and the tab/mermaid helpers incore.py.
What to do after you find a selector URL copied
Once you have the selector (id/class/tag), add a rule in custom.css (or a folder custom.css if you want section‑only styling), then reload and refine.
Example:
<aside id="posts-sidebar" class="hidden xl:block w-72 ...">
/* Root custom.css */
#posts-sidebar {
background: #f3f4f6;
border-radius: 12px;
padding: 0.5rem;
}
If your rule doesn’t apply:
- Increase specificity: target a deeper element (e.g.,
#posts-sidebar ul) - Add
!importantto the property being overridden - Check scope: if you used folder
custom.css, confirm the page path matches the section
Page structure URL copied
#page-container- outer wrapper for the whole page#site-navbar/.vyasa-navbar-shell- sticky header wrapper.vyasa-navbar-card- actual visible navbar card#content-with-sidebars- row containing sidebars + main content#main-content/.vyasa-main-shell- the rendered post content#site-footer/.vyasa-footer-shell- footer wrapper.vyasa-footer-card- actual visible footer card
Sidebars URL copied
#posts-sidebar/.vyasa-posts-sidebar- left navigation tree shell#toc-sidebar/.vyasa-toc-sidebar- right table of contents shell.vyasa-sidebar-card- collapsible sidebar card.vyasa-sidebar-toggle- sidebar summary/header row.vyasa-sidebar-body- sidebar content panel#sidebar-scroll-container- scrollable list container.toc-link- each TOC link
Mobile panels URL copied
#mobile-posts-panel,#mobile-toc-panel,.vyasa-mobile-panel- off-canvas panels.vyasa-mobile-panel-header,.vyasa-mobile-panel-body- mobile panel sections#mobile-posts-toggle,#mobile-toc-toggle- toggle buttons
Markdown content URL copied
h1…h6,p,ul,ol,blockquote,table,code,pre,img.mermaid-wrapperand.mermaidfor Mermaid diagrams.sidenote-ref,.sidenote,.sidenote.hlfor footnotes.tabs-container,.tabs-header,.tab-button,.tabs-content,.tab-panelfor tabs
4) Global theming (background, typography, links) URL copied
Put this in root custom.css to define a new global look:
/* Global background + text */
html, body {
background-color: #f6f3ee !important;
color: #1f2937 !important;
}
#page-container,
#main-content {
background-color: transparent;
color: inherit;
}
.dark html, .dark body {
background-color: #0b0f14 !important;
color: #e2e8f0 !important;
}
/* Typography */
body {
font-family: "IBM Plex Sans", system-ui, sans-serif;
line-height: 1.7;
}
h1, h2, h3 {
letter-spacing: -0.02em;
}
/* Links */
a { color: #0f766e; }
a:hover { color: #115e59; }
5) Navbar and footer URL copied
Use the explicit hooks instead of positional selectors. Style the visible navbar through .vyasa-navbar-card, not #site-navbar > *.
.vyasa-navbar-card {
background-color: #0f766e !important;
color: #f8fafc !important;
}
.vyasa-footer-card {
background-color: #1f2937 !important;
color: #f8fafc !important;
}
#site-navbar a,
#site-footer a {
color: #f8fafc;
}
#site-navbar a:hover,
#site-footer a:hover {
color: #e2e8f0;
}
.dark .vyasa-navbar-card { background-color: #0b3b3a !important; }
.dark .vyasa-footer-card { background-color: #111827 !important; }
6) Sidebars and TOC URL copied
/* Left posts sidebar */
.vyasa-posts-sidebar .vyasa-sidebar-body {
background: #f3f4f6;
border-radius: 12px;
padding: 0.5rem;
}
/* TOC sidebar */
.vyasa-toc-sidebar .vyasa-sidebar-body {
background: #f8fafc;
border-radius: 12px;
padding: 0.5rem;
}
/* TOC links */
.toc-link {
color: #0f172a !important;
border-radius: 8px;
}
.toc-link:hover {
background: rgba(15, 118, 110, 0.12);
color: #0f766e !important;
}
7) Code blocks and inline code URL copied
pre {
background: #0b1020;
color: #e2e8f0;
border-radius: 12px;
padding: 1rem 1.25rem;
}
code {
background: rgba(15, 118, 110, 0.12);
color: #0f766e;
padding: 0.1rem 0.35rem;
border-radius: 6px;
}
pre code {
background: transparent;
color: inherit;
padding: 0;
}
8) Mermaid diagrams URL copied
.mermaid-wrapper {
background: #f8fafc;
border-radius: 12px;
border: 1px solid #e2e8f0;
}
.dark .mermaid-wrapper {
background: #0f172a;
border-color: #1f2937;
}
9) Footnotes / sidenotes URL copied
.sidenote-ref {
font-size: 0.8rem;
padding: 0 0.2rem;
border-radius: 0.25rem;
}
.sidenote {
font-size: 0.95rem;
color: #334155;
}
.sidenote.hl {
background-color: rgba(15, 118, 110, 0.12);
}
10) Tabs URL copied
Tabs are styled by built-in CSS, but you can override:
.tabs-container {
border-radius: 14px;
border-color: #cbd5f5;
}
.tab-button.active {
border-bottom-color: #0f766e;
color: #0f766e;
}
.tabs-content {
background: #ffffff;
}
11) Images and media URL copied
img {
border-radius: 12px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
}
figure > img {
width: 100%;
height: auto;
}
12) Change behavior (JS + HTML) URL copied
For behavior changes (animations, interactions, logic), you have two options:
Option A: Custom JS URL copied
Edit:
vyasa/static/scripts.jsfor live appvyasa/build.py(static build) if you need it in static exports
Option B: Inline HTML/JS in Markdown URL copied
If you need per-post behavior, you can embed raw HTML in markdown:
<div id="my-widget"></div>
<script>
// Your custom behavior here
</script>
13) Advanced: change the HTML structure URL copied
If you want to move elements or change layout, edit these functions:
vyasa/core.py::navbar()for the header markupvyasa/core.py::layout()for page structure and sidebarsvyasa/build.py::static_layout()for static builds
14) Troubleshooting URL copied
If your styles don't apply:
- Check your root: Is
custom.cssin the actual blog root? - Check if CSS is loaded: View page source and confirm
/posts/custom.cssis present. - HTMX swaps: Folder-scoped CSS is injected into
#scoped-css-containerand swapped during navigation. If your styles disappear, ensurecustom.cssexists in that folder. - Utility class conflicts: Use more specific selectors or
!importantto override Tailwind-style classes.
You now have full control of Vyasa's look and behavior. Start global, then layer scoped CSS for sections, then override specific elements by id/class as needed.